Kubernetes Pod 删除操作源码解析
The following article is from k8s技术圈 Author 阳明
比如现在我有一个更新策略为 Recreate
的应用,然后执行删除命令,如下所示:
☸ ➜ kubectl get pods
NAME READY STATUS RESTARTS AGE
minio-875749785-sv5ns 1/1 Running 1 (2m52s ago) 42h
☸ ➜ kubectl delete pod minio-875749785-sv5ns
pod "minio-875749785-sv5ns" deleted
在删除之前在另外一个终端观察应用状态:
☸ ➜ kubectl get pods -w
NAME READY STATUS RESTARTS AGE
minio-875749785-sv5ns 1/1 Running 1 (2m46s ago) 42h
minio-875749785-sv5ns 1/1 Terminating 1 (2m57s ago) 42h
minio-875749785-h2j2b 0/1 Pending 0 0s
minio-875749785-h2j2b 0/1 Pending 0 0s
minio-875749785-h2j2b 0/1 ContainerCreating 0 0s
minio-875749785-sv5ns 0/1 Terminating 1 (2m59s ago) 42h
minio-875749785-sv5ns 0/1 Terminating 1 (2m59s ago) 42h
minio-875749785-sv5ns 0/1 Terminating 1 (2m59s ago) 42h
minio-875749785-h2j2b 0/1 Running 0 17s
minio-875749785-h2j2b 1/1 Running 0 30s
从上面的过程可以看到当我们执行 kubectl delete
命令后 Pod 变成了 Terminating
状态,然后才消失。接下来我们会从代码角度来介绍下删除 Pod 的整体流程。
这里我们以
v1.22.8
版本的 Kubernetes 为例进行说明,其他版本不保证代码完全一致,但是整体思路是一致的。
删除状态
我们可以根据 kubectl 操作后看到的状态来进行跟踪,上面的格式化结果是通过代码 https://github.com/kubernetes/kubernetes/blob/v1.22.8/pkg/printers/internalversion/printers.go#L88-L102 实现的,如下所示:
对于 Pod 的输出结果是通过 printPod
函数获取的,代码位于:https://github.com/kubernetes/kubernetes/blob/v1.22.8/pkg/printers/internalversion/printers.go#L756-L840,其中有一段代码提到了 Terminating
值,是在 pod.DeletionTimestamp != nil
的情况下变成该状态的,如下所示:
也就是说当执行删除操作的时候,会设置 Pod 的 DeletionTimestamp
属性,这个时候就会显示成 Terminating
状态。
当执行删除操作的时候,会向 apiserver 发送一次 DELETE 请求:
I0408 11:25:33.002155 42938 round_trippers.go:435] curl -v -XDELETE -H "Content-Type: application/json" -H "User-Agent: kubectl/v1.22.7 (darwin/amd64) kubernetes/b56e432" -H "Accept: application/json" 'https://192.168.0.111:6443/api/v1/namespaces/default/pods/minio-875749785-sv5ns'
I0408 11:25:33.037245 42938 round_trippers.go:454] DELETE https://192.168.0.111:6443/api/v1/namespaces/default/pods/minio-875749785-sv5ns 200 OK in 35 milliseconds
接收到删除请求的处理器位于代码 https://github.com/kubernetes/kubernetes/blob/v1.22.8/staging/src/k8s.io/apiserver/pkg/registry/generic/registry/store.go#L986,如下所示:
在 BeforeDelete
函数中判断是否需要优雅删除,判断的标准是 DeletionGracePeriodSeconds
值是否为 0,不为零则认为是优雅删除,apiserver 不会立即将这个对象从 etcd 中删除,否则直接删除。对于 Pod 而言,默认 DeletionGracePeriodSeconds
为 30 秒,因此这里不会被立刻删除掉,而是将 DeletionTimestamp
设置为当前时间,DeletionGracePeriodSeconds
设置为默认值 30 秒。代码位于 https://github.com/kubernetes/kubernetes/blob/v1.22.8/staging/src/k8s.io/apiserver/pkg/registry/rest/delete.go#L93-L159,在该函数中会设置 DeletionTimestamp
的值,如下所示:
上面的代码验证了当执行删除操作的时候,apiserver 会先设置 Pod 的 DeletionTimestamp
属性为当前时间加上优雅删除宽限时长的时间点,设置了该属性后,我们客户端格式化过后看到的就是 Terminating
状态了。
优雅删除
由于 Pod 中涉及到其他很多资源,比如 sandbox 容器、volume 卷等等,在删除后都需要进行回收,而删除 Pod 最终也是去删除对应的容器,这个就需要 Pod 所在节点的 kubelet 来完成清理了。kubelet 首先同样会一直 watch 我们的 Pod,当 Pod 的删除时间更新后,自然就会接收到事件,然后进行相应的清理工作。
kubelet 对 Pod 的处理主要在 syncLoop
函数中,会去调用和事件相关的处理函数 syncLoopIteration
,代码位于 https://github.com/kubernetes/kubernetes/blob/v1.22.8/pkg/kubelet/kubelet.go#L2040-L2079 中,如下所示:
当执行删除操作的时候,apiserver 首先会更新 Pod 中的 DeletionTimestamp
属性,这个改变对于 kubelet 来说属于更新操作,所以会对应 kubetypes.UPDATE
操作,会调用 HandlePodUpdates
函数进行更新。
在 HandlePodUpdates
中会调用 dispatchWork
将 Pod 删除分配给具体的 worker 处理,podWorker 是具体的执行者,也就是每次 Pod 需要更新都会发送给 podWorker。
dispatchWork
方法会调用 UpdatePod
函数对 Pod 进行删除,代码位于 https://github.com/kubernetes/kubernetes/blob/v1.22.8/pkg/kubelet/pod_workers.go#L540-L765,在该函数中会通过一个 channel 传递 Pod 信息,在一个 goroutine 中调用 managePodLoop
函数进行处理,该函数中会调用 syncTerminatingPod/syncPod
方法来进行删除操作。
最终都会调用 killPod
函数去执行删除 Pod:
killPod
函数中会调用容器运行时去停止该 Pod 中的容器,代码位于https://github.com/kubernetes/kubernetes/blob/v1.22.8/pkg/kubelet/kubelet_pods.go#L856-L868:
容器运行时的 KillPod 方法位于 https://github.com/kubernetes/kubernetes/blob/v1.22.8/pkg/kubelet/kuberuntime/kuberuntime_manager.go#L969-L998,如下所示:
killPodWithSyncResult
方法中首先调用函数 killContainersWithSyncResult
杀掉所有运行的容器,然后删除 Pod 的 sandbox。
在该函数中,利用多个 goroutine 来对 Pod 中的每一个容器进行删除,删除容器的方法是 killContainer
,在该函数中首先会执行 pre-stop 这个 hooks(如果存在的话),然后才停止容器,代码位于 https://github.com/kubernetes/kubernetes/blob/v1.22.8/pkg/kubelet/kuberuntime/kuberuntime_container.go#L660-L736。
首先获取优雅删除的宽限时间:
其中 TerminationGracePeriodSeconds
可以在资源清单文件中进行设置,默认为 30 秒,这个时间是,给 Pod 发出关闭指令后会给应用发送 SIGTERM 信号,程序只需要捕获 SIGTERM 信号并做相应处理即可。也就是 Pod 接收到 SIGTERM 信号后,应用能够优雅关闭的时间。该时间是由 apiserver 设置的,前面已经分析过。
如果配置了 pre-stop hook 并且还有足够的时间,则会执行该 hook,pre-stop 主要是为了业务在容器删除前前,能够优雅的停止,比如资源回收等操作:
最后才会真正去调用底层容器运行时来停止容器:
容器删掉后回到前面的 killPodWithSyncResult
函数中,接下来就会去调用运行时服务的 StopPodSandbox
函数停止 sandbox 容器,也就是 pause 容器。
// Stop all sandboxes belongs to same pod
for _, podSandbox := range runningPod.Sandboxes {
if err := m.runtimeService.StopPodSandbox(podSandbox.ID.ID); err != nil && !crierror.IsNotFound(err) {
killSandboxResult.Fail(kubecontainer.ErrKillPodSandbox, err.Error())
klog.ErrorS(nil, "Failed to stop sandbox", "podSandboxID", podSandbox.ID)
}
}
到这里 kubelet 就完成了对 Pod 的优雅删除,但是这并没有结束。
同步状态
对于优雅删除一开始在 apiserver 只是给 Pod 设置了 DeletionTimestamp
属性,然后 kubelet watch 来更新后去完成了 Pod 的优雅删除,但是现在服务端中还有 Pod 的记录,并没有真正去删除。
在 kubelet 启动的时候同时还去启动了一个 statusManager 的同步循环,该 Manager 是 kubelet pod 状态的真实来源,应该与最新的 v1.PodStatus
保持同步,它还将更新同步回 apiserver,也就是当优雅删除完成后我们还将通过该管理器将状态同步回 apiserver。
状态管理器在与 apiserver 进行状态同步的时候会去调用该管理器下面的 syncPod
方法进行处理,代码位于 https://github.com/kubernetes/kubernetes/blob/v1.22.8/pkg/kubelet/status/status_manager.go#L149-L181,如下所示:
在该方法中会判断 Pod 是否已经优雅停止了,代码位于 https://github.com/kubernetes/kubernetes/blob/v1.22.8/pkg/kubelet/status/status_manager.go#L583-L652,如下所示:
比如会判断是否还有容器在运行、volumes 是否还没有清理、pod cgroup 还没清空等等,如果 canBeDeleted
返回 true,则表示 pod 已经优雅的停止了,那么这个时候就可以向 apiserver 发送 Delete 请求,再次删除 Pod 了。
不过这一次的设置的 GracePeriodSeconds
为 0,表示要强制删除 Pod 了,到这里 apiserver 会再次收到 DELETE 请求,与第一次不同的是,这次是强制删除 Pod,会去 etcd 中删除 Pod 对象了。
这个时候 kubelet 会接受到 REMOVE 的事件,调用 HandlePodRemoves
函数去进行处理:
首先会去调用 deletePod
函数去停掉关联的 pod worker,然后还会调用 probeManager
去移除 Pod 相关的探针 prober worker,到这里就表示 Pod 彻底从节点上删除了。
推荐阅读
还怕记不住 Kubectl 命令?K9s 太强大了Kubernetes 微服务最佳实践
使用 Lux 下载B站视频,真强大
38 万K8s API 服务暴露在公网上可能被攻击?
大规模 K8s 集群性能瓶颈和调优实践
优雅的跨 Namespace 同步 Secret 和 ConfigMap?
Kruise 轻松让 K8S 应用实现渐进式交付图解 Kubernetes Pod 如何获取 IP 地址使用 Telepresence 轻松在本地调试 k8s 应用程序GitOps 工具选型,33 款工具任你挑!就在刚刚 k8s 1.24 正式发布,来看功能总览Nomad 会替代 Kubernetes 吗?两者如何选择?Docker 入门终极指南,详细版!漫画轻松看懂如何用 Kubernetes 实现 CI/CD阿里开源的低代码引擎,已收获 4.5K 星星新手必须知道的 Kubernetes 架构Kubernetes 架构核心点详细总结!